﻿#region Header
// Copyright 2007-2011, Jerónimo Milea
// 
// This file is part of NetJsWire.
// 
//     NetJsWire is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
// 
//     NetJsWire is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
// 
//     You should have received a copy of the GNU General Public License
//     along with NetJsWire.  If not, see <http://www.gnu.org/licenses/>.
#endregion

using System;
using System.Reflection;
using System.Web;
using NetJsWire.Direct.attributes;
using NetJsWire.Direct.baseclasses;
using NetJsWire.Direct.exceptions;
using NetJsWire.Direct.helper;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace NetJsWire.Direct
{
    internal class DirectMethod
    {
        internal DirectMethodType MethodType;

        internal DirectMethod( MethodInfo method, DirectMethodType methType, DirectAction parentAction )
            : this( method, methType, parentAction, method.Name )
        {



            // this.Parameters = method.GetParameters().Length;
        }

        /// <summary>
        ///   Creates an instance of this class.
        /// </summary>
        /// <param name = "method">The method information.</param>
        internal DirectMethod( MethodInfo method, DirectMethodType methType, DirectAction parentAction, string directMethodName )
        {
            ParentAction = parentAction;
            MethodType = methType;
            Method = method;
            IsForm = ( methType == DirectMethodType.Form ); // FIX Utility.HasAttribute(method, typeof(DirectMethodFormAttribute));
            Name = directMethodName;

            MethodAttributes = ( ( DirectMethodAttribute ) Method.GetCustomAttributes( typeof( DirectMethodAttribute ), true )[ 0 ] );


            // this.Parameters = method.GetParameters().Length;
        }

        /// <summary>
        ///   Gets the method info.
        /// </summary>
        internal MethodInfo Method
        {
            get;
            private set;
        }

        internal DirectMethodAttribute MethodAttributes
        {
            get;
            set;
        }

        internal DirectAction ParentAction
        {
            get;
            private set;
        }

        /// <summary>
        ///   Gets whether the method is a form method;
        /// </summary>
        internal bool IsForm
        {
            get;
            private set;
        }

        /// <summary>
        ///   Gets the name of the method.
        /// </summary>
        internal string Name
        {
            get;
            private set;
        }

        /// <summary>
        ///   Gets the number of parameters for the method.
        /// </summary>
        internal int Parameters
        {
            get
            {

                if ( MethodAttributes.ParameterHandling == ParameterHandling.PassThrough &&
                    MethodType == DirectMethodType.Normal )
                {
                    return Method.GetParameters().Length;
                }
                else if ( MethodAttributes.ParameterHandling == ParameterHandling.AutoResolve )
                {
                    switch ( MethodType )
                    {
                        case DirectMethodType.Normal:
                            return 1;

                    }
                }
                switch ( MethodType )
                {
                    case DirectMethodType.Create:
                        return 1;
                    case DirectMethodType.Read:
                        return 1;
                    case DirectMethodType.Delete:
                        return 1;
                    case DirectMethodType.Form:
                        return 1;
                    case DirectMethodType.Update:
                    case DirectMethodType.TreeLoad:
                        return 2;

                }
                return Method.GetParameters().Length;
            }
        }

        internal OutputHandling OutputHandling
        {
            get
            {
                var da = ( ( DirectMethodAttribute ) Method.GetCustomAttributes( typeof( DirectMethodAttribute ), true )[ 0 ] );
                return da.OutputHandling;
            }
        }

        private Object[] ResolveParametersByIndex( DirectRequest directRequest )
        {
            var parmInfo = Method.GetParameters();
            var paramMap = new object[ parmInfo.Length ];

            if ( !( directRequest.Data is JValue ) )
            {

                var parameter = ( ( JArray ) directRequest.Data );
                //if ( directRequest.Data.ToString().Contains( "{" ) )
                //{
                //    return ResolveParametersByName( directRequest, parameter );
                //}

                ////CHECK: I'm not really sure when this can happen, DirectActions allways send named parameters, so the need for this is uncertain...
                var i = 0;
                // use the Position as Indicator
                for ( i = 0; i < parameter.Count && i < paramMap.Length; i++ )
                {

                    var type = parmInfo[ i ].ParameterType;

                    try
                    {
                        if ( type == Type.GetType( "System.Object" ) )
                        {
                            paramMap[ i ] = parameter[ i ];
                        }
                        else
                        {
                            paramMap[ i ] = JsonConvert.DeserializeObject( parameter[ i ].ToString(), type );
                        }


                    }
                    catch ( Exception e )
                    {
                        throw ( new DirectParameterException(
                            "Illegal Argument: There did an Exception Occur while trying to Deserialze the parameter " +
                            parmInfo[ i ].Name + " of the type " + parmInfo[ i ].ParameterType +
                            " from json: " + parameter[ i ], directRequest ) );
                    }

                }
            }

            return paramMap;
        }

        private Object[] ResolveParametersByName( DirectRequest directRequest )
        {
            var parameter = ( ( JArray ) directRequest.Data );
            return ResolveParametersByName( directRequest, parameter );
        }

        private Object[] ResolveParametersByName( DirectRequest directRequest, JArray parameter )
        {
            var parmInfo = Method.GetParameters();
            var paramMap = new object[ parmInfo.Length ];

            if ( parameter != null )
            {
                for ( var i = 0; i < parameter.Count; i++ )
                {
                    Object curParam = parameter[ i ];
                    if ( curParam is JObject )
                    {
                        var i2 = 0;

                        foreach ( var parm in parmInfo )
                        {
                            if ( ( ( JObject ) curParam )[ parm.Name ] != null )
                            {
                                Object curentParameter = ( ( JObject ) curParam )[ parm.Name ];

                                if ( curentParameter != null )
                                {

                                    var type = parmInfo[ i2 ].ParameterType;
                                    try
                                    {
                                        if ( type == Type.GetType( "System.Object" ) )
                                        {
                                            if ( curentParameter is long )
                                                curentParameter = Convert.ToInt32( curentParameter );

                                            paramMap[ i2 ] = curentParameter;
                                        }
                                        else if ( type == typeof( String ) )
                                        {
                                            paramMap[ i2 ] = curentParameter.ToString();
                                        }
                                        else if ( type == typeof( object[] ) && type.IsArray )
                                        {
                                            var strParam = curentParameter.ToString();
                                            if ( !strParam.StartsWith( "[" ) )
                                                strParam = "[" + strParam + "]";
                                            var pars = JsonConvert.DeserializeObject( strParam, type );
                                            var index = 0;
                                            var o = ( object[] ) pars;
                                            foreach ( var p in pars as object[] )
                                            {
                                                if ( p is long )
                                                    o[ index ] = Convert.ToInt32( p );
                                                index++;
                                            }

                                            paramMap[ i2 ] = o;
                                        }
                                        else
                                        {
                                            var strParam = curentParameter.ToString().ToLower();
                                            if ( type.IsArray && !strParam.StartsWith( "[" ) )
                                                strParam = "[" + strParam + "]";
                                            if ( !string.IsNullOrEmpty( strParam ) )
                                                paramMap[ i2 ] = JsonConvert.DeserializeObject( strParam );
                                            else
                                                paramMap[ i2 ] = null;
                                        }
                                    }
                                    catch ( Exception e )
                                    {
                                        throw ( new DirectParameterException( "Illegal Argument: There did an Exception Occur while tryng to Deserialze the parameter " +
                                            parmInfo[ i2 ].Name + " of the type " + parmInfo[ i2 ].ParameterType + " from json: " + parameter[ i2 ], directRequest ) );
                                    }
                                }
                            }
                            i2++;
                        }
                    }
                }
            }
            return paramMap;
        }

        /// <summary>
        ///   Since a Update Function has another parameter Structure we need our own resolve logic function here
        /// </summary>
        /// <param name = "directRequest"></param>
        /// <returns></returns>
        private Object[] ResolveUpdateParameter( DirectRequest directRequest )
        {
            var parmInfo = Method.GetParameters();
            var paramMap = new object[ parmInfo.Length ];

            var parameter = ( ( JArray ) directRequest.Data );
            if ( parameter != null )
            {
                if ( parameter.Count < 2 )
                {
                    throw new DirectParameterException( "Tried to call an Updatemethod with less than 2 Parameters.", directRequest );
                }
                var paramTyp = parmInfo[ 0 ].ParameterType;

                var id = new Object();
                //paramMap[0] = ((paramTyp)parameter[0]);
                if ( parameter[ 0 ] is JValue )
                {

                    try
                    {
                        if ( paramTyp == Type.GetType( "System.Object" ) )
                        {
                            id = parameter[ 0 ];
                        }
                        else
                        {
                            id = JsonConvert.DeserializeObject( parameter[ 0 ].ToString(), paramTyp );
                        }
                    }
                    catch ( Exception e )
                    {
                        throw ( new DirectParameterException( "Illegal Argument: There did an Exception Occur while tryng to Desirialze the parameter " +
                            parmInfo[ 0 ].Name + " of the type " + parmInfo[ 0 ].ParameterType + " from json: " + parameter[ 0 ], directRequest ) );
                    }

                }

                var tempParams = new JArray();

                tempParams.Add( parameter[ 1 ] );

                paramMap = ResolveParametersByName( directRequest, tempParams );

                paramMap[ 0 ] = id;

            }
            return paramMap;

        }

        /// <summary>
        ///   Calls this Direct Methods and parses the Parameter as via the customAttribute DirectMethodAttribute 
        ///   configured
        /// </summary>
        /// <param name = "parameter">An Object of Parametr</param>
        /// <returns></returns>
        internal Object invoke( DirectRequest directRequest, HttpContext httpContext )
        {

            var parmInfo = Method.GetParameters();

            // will contain the paramters the function gets called with
            var paramMap = new object[ parmInfo.Length ];


            if ( directRequest.HttpRequest != null )
            {
                // do the methode wants to take care of the request by it self?
                if ( parmInfo[ 0 ].ParameterType == directRequest.HttpRequest.GetType() )
                {
                    paramMap[ 0 ] = directRequest.HttpRequest;
                }
                else
                {
                    var curParam = directRequest.HttpRequest;
                    var i2 = 0;
                    // try to find Parameters in the Formvariables
                    foreach ( var parm in parmInfo )
                    {
                        var type = parmInfo[ i2 ].ParameterType;
                        if ( type.Name == "HttpPostedFile" )
                        {

                            i2++;
                            continue;
                        }
                        var curentParameter = ( curParam ).Form[ parm.Name ];
                        if ( curentParameter == null )
                        {

                            i2++;
                            continue;
                        }
                        try
                        {
                            if ( type == Type.GetType( "System.Object" ) || type == Type.GetType( "System.String" ) )
                            {
                                paramMap[ i2 ] = curentParameter;
                            }
                            else
                            {
                                paramMap[ i2 ] = JsonConvert.DeserializeObject( curentParameter, type );
                            }
                        }
                        catch ( Exception e )
                        {
                            throw ( new DirectParameterException( "Illegal Argument: There did an Exception Occur while tryng to Desirialze the parameter " +
                                parmInfo[ i2 ].Name + " of the type " + parmInfo[ i2 ].ParameterType + " from json: " + curentParameter, directRequest ) );
                        }

                        i2++;
                    }

                    i2 = 0;
                    // try to find Parameters in the Files-list
                    foreach ( var parm in parmInfo )
                    {

                        var type = parmInfo[ i2 ].ParameterType;
                        if ( ( curParam ).Files[ parm.Name ] != null )
                        {
                            if ( type.Name != "HttpPostedFile" )
                            {
                                throw ( new DirectParameterException( "Illegal Argument: The Parameter " + parm.Name + " is not an instance of \"System.WebHttpPosted\" File.", directRequest ) );
                            }
                            paramMap[ i2 ] = ( curParam ).Files[ parm.Name ];
                        }
                        i2++;
                    }
                }


            }
            else
                if ( MethodAttributes.ParameterHandling == ParameterHandling.PassThrough )
                {
                    paramMap = ResolveParametersByIndex( directRequest );

                }
                else
                {
                    switch ( MethodAttributes.MethodType )
                    {
                        case DirectMethodType.Normal:
                        case DirectMethodType.Create:
                        case DirectMethodType.Read:
                        case DirectMethodType.Delete:
                        case DirectMethodType.Hybrid:
                            paramMap = ResolveParametersByName( directRequest );
                            break;
                        case DirectMethodType.Update:
                        case DirectMethodType.TreeLoad: // use same function cause structure is simmilar
                            paramMap = ResolveUpdateParameter( directRequest );
                            break;

                    }
                }







            // will contain the paramters the function gets called with



            // this is not really usefull since the deserialization has allready been done...
            // maybe for forms?
            // if(da.SerializeParameterTypes){
            // serialize the attributes before invoking the method
            //}

            var actionClassType = ParentAction.Type;
            object actionInstanz = null;
            
            // If Method is static, we don't need to instantiate the class
            // If class is abstract and sealed (or static for that matter) it cannot be instantiated
            if ( !Method.IsStatic && !( actionClassType.IsAbstract && actionClassType.IsSealed ) )
            {
                try
                {
                    actionInstanz = actionClassType.Assembly.CreateInstance( actionClassType.FullName );
                    if ( actionInstanz is IActionWithSessionState )
                    {
                        ( ( IActionWithSessionState ) actionInstanz ).SetSession( httpContext.Session );
                    }

                    if ( actionInstanz is IActionWithServer )
                    {
                        ( ( IActionWithServer ) actionInstanz ).SetServer( httpContext.Server );
                    }

                }
                catch ( MissingMethodException )
                {
                    //Ignore this exception, if the action was collected by reflection then, by all means, method is there, class is static so it has no constructor...
                }
            }
            return Method.Invoke( actionInstanz, paramMap );
        }

        /// <summary>
        ///   Write API JSON.
        /// </summary>
        /// <param name = "jw">The JSON writer.</param>
        internal void Write( JsonTextWriter jw )
        {
            jw.WriteStartObject();
            Utility.WriteProperty( jw, "name", Name );
            Utility.WriteProperty( jw, "len", Parameters );
            Utility.WriteProperty( jw, "formHandler", IsForm );
            jw.WriteEndObject();
        }

        /// <summary>
        ///   Checks whether the passed method is a direct method.
        /// </summary>
        /// <param name = "mi">The method to check.</param>
        /// <returns>True if the method is a direct method.</returns>
        internal static bool IsMethod( MethodInfo mi )
        {
            return Utility.HasAttribute( mi, typeof( DirectMethodAttribute ) );
        }

    }
}
